package de.bazinga.rotw.person;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;
import java.util.Vector;

import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jmex.game.state.GameStateManager;
import com.jmex.jbullet.PhysicsSpace;
import com.jmex.jbullet.collision.shapes.CollisionShape;
import com.jmex.jbullet.nodes.PhysicsNode;
import com.jmex.model.animation.JointController;

import de.bazinga.rotw.game.Game;
import de.bazinga.rotw.game.RotwGameState;
import de.bazinga.rotw.gameModes.CaptureTheZombie;
import de.bazinga.rotw.model.ModelCache;
import de.bazinga.rotw.network.BasicSyncManager;
import de.bazinga.rotw.network.Client;
import de.bazinga.rotw.network.Server;
import de.bazinga.rotw.network.message.KilledOppMessage;
import de.bazinga.rotw.network.message.ZombieInfoMessage;
import de.bazinga.rotw.player.Player;
import de.bazinga.rotw.treasure.Treasure;
import de.bazinga.rotw.treasure.TreasureResetMessage;
import de.bazinga.rotw.util.GlobalSettings;
import de.bazinga.rotw.vehicle.Vehicle;
import de.bazinga.rotw.weapon.Knife;
import de.bazinga.rotw.weapon.Pistol;
import de.bazinga.rotw.weapon.Rifle;
import de.bazinga.rotw.weapon.Uzi;
import de.bazinga.rotw.weapon.Weapon;
import de.bazinga.rotw.weapon.grenade.HEGrenade;
import de.bazinga.rotw.weapon.grenade.SmokeGrenade;
import de.bazinga.rotw.weapon.portal.PortalGun;

public class Person extends Node {
	private static final long serialVersionUID = 1L;
	
	public static final String TEXTURE_BLUE = "blue";
	public static final String TEXTURE_WHITE = "white";
	public static final String TEXTURE_BROWN = "brown";
	public static final String TEXTURE_GREEN = "green";
	public static final String TEXTURE_RED = "red";
	
	
	
	private Vector3f walkingDirection = new Vector3f();
	

	protected String textureColor = "green";
	
	
	public static final int TEAM_ONE = 1;
	public static final int TEAM_TWO = 2;
	
	protected int team  = 1;
	
	
	protected boolean hasZombie = false;
	
	
	
	protected Vehicle vehicle = null;
	

	

	protected String playerName = "Anonymous";
	protected short playerId = -1;

	
	/**
	 * This Node contains the Player Model
	 */
	protected Node body = new Node("playerBody");
	
	
	/**
	 * the physics-Node
	 * (only Person uses it, player creates its own physics
	 */
	protected PhysicsNode bodyPhysicsNode = null;
	
	/**
	 * Does all the Player Animation
	 */
	protected PersonAnimation animation = null;
	

	/**
	 * The Possible Start-Locations
	 */
	protected Hashtable<Integer, Vector<Vector3f>> startPos = null;
	
	
	protected int health = 100;
	protected long lastHealthInc = 0;
	
	protected int armor = 0;
	protected int score = 0;
	

	protected long lastShoot = 0;
	protected ColorRGBA bulletColor = ColorRGBA.red;
	
	
	protected long lastTimeDied = 0;
	protected boolean resetAfterDeath = false;

	
	/**
	 * Weapon-Stuff
	 */
	protected Vector<Weapon> weapons = new Vector<Weapon>();
	protected int currentWeapon = 0;
	protected PortalGun portalGun = null;
	
	/**
	 * The Last time he picked up the doubleDamage item
	 */
	protected long doubleDamageTime = 0;
	protected static final int DOUBLEDAMAGEDURATION = 60000;
	
	
	/**
	 * The Factor we multiply the damage of a weapon with
	 */
	protected int damageFactor = 1;
	
	
	protected Box teamBox = new Box("test",new Vector3f(-0.1f,2.4f,-0.1f), new Vector3f(0.1f,2.6f,0.1f));
	
	
	/**
	 * Contains all the Treasures the player found
	 */
	protected Vector<Treasure> treasuresFound = new Vector<Treasure>();
	
	
	/**
	 * Is the player in Godmode?
	 */
	protected boolean godMode = false;
	
	
	public Person(String name, String textureColor, int team) {
		super(name);
		
		this.playerName = name;
		this.textureColor = textureColor;
		this.team = team;
		
		//this.ammo = GlobalSettings.MAXAMMO;

		// Set Start-Positions
		this.startPos = new Hashtable<Integer,Vector<Vector3f>>();
		Vector<Vector3f> teamPos = new Vector<Vector3f>();
		teamPos.add(new Vector3f(0,0,0));
		startPos.put(1, teamPos);
		startPos.put(2, teamPos);
		
		loadModel();
		

	    // Attach TeamBox
        teamBox.setRandomColors();
        teamBox.setModelBound(new BoundingBox());
        teamBox.updateModelBound();
        teamBox.setCullHint(CullHint.Never);
        teamBox.setLightCombineMode(Spatial.LightCombineMode.Off);
        this.attachChild(teamBox);
        
        
        
		
        // Create Weapons
        resetWeapons();
	}
	
	
	protected void loadModel() {
		if(body != null) {
			if(animation != null) body.removeController(animation.getController());
			if(hasChild(body)) this.detachChild(body); 
			body = new Node("playerBody");
		}
		
		
		// Load Model
		URL ninjaUrl = null;
		URL textureUrl = null;
		try {
			ninjaUrl = new File("player/soldier/").toURI().toURL();
			textureUrl = new File("player/soldier/"+textureColor+"/").toURI().toURL();
		} catch (MalformedURLException e1) {
			e1.printStackTrace();
		}

		Node ant = ModelCache.getInstance().loadModel(textureUrl, ninjaUrl, "/player/soldier/prova.ms3d");
		
        //ant.setLocalScale(0.24f);
        
        
        body.attachChild(ant);
        
        JointController controller = (JointController)ant.getController(0);
        controller.setRepeatType(Controller.RT_CYCLE);
        controller.setSpeed(0.5f);
        controller.setActive(false);
        
        
        animation = new PersonAnimation(this, controller);
		
        body.setModelBound(new BoundingBox());
        body.updateModelBound();
        
        this.attachChild(body);
        // Move the Character down so the BoundingSphere fits better
        body.getLocalTranslation().subtractLocal(new Vector3f(0,5f,0));
        this.setModelBound(new BoundingSphere());
        this.updateModelBound();
        
		//this.attachChild(body);
	}
	
	
	public void createBodyPhysicsNode(Node scene) {
		bodyPhysicsNode = new PhysicsNode(this,CollisionShape.ShapeTypes.BOX,300);			
		bodyPhysicsNode.setLocalScale(0.24f);
		bodyPhysicsNode.setKinematic(true);
		
		scene.attachChild(bodyPhysicsNode);

		bodyPhysicsNode.updateRenderState();
		// Add ground to physicsNode
		PhysicsSpace.getPhysicsSpace().add(bodyPhysicsNode);
	}
	
	
	public void setLocalTranslation(Vector3f trans) {
		if(bodyPhysicsNode != null) {
			bodyPhysicsNode.setLocalTranslation(trans.x, trans.y, trans.z);
		}else{
			super.setLocalTranslation(trans);
		}
	}
	
	public Vector3f getLocalTranslation() {
		if(bodyPhysicsNode != null) {
			return bodyPhysicsNode.getLocalTranslation();
		}else{
			return super.getLocalTranslation();
		}
	}
	
	
	/**
	 * Get the Camera-Position
	 * @return position of the Camera
	 */
	public Vector3f getCameraPosition() {
		if(hasVehicle()) {
			return getVehicle().getCameraLocation();
		}
		
		if(animation.isDucking()) {
			return new Vector3f(this.getLocalTranslation().x,
					this.getLocalTranslation().y  + .7f,
					this.getLocalTranslation().z);
		}
		
		return new Vector3f(this.getLocalTranslation().x,
				this.getLocalTranslation().y  + 1.3f,
				this.getLocalTranslation().z).subtract(Game.getGame().getCamera().getDirection().mult(0.5f));
	}
	
	
	


	/**
	 * Wrapper for reset(boolean)
	 * Will reset the score
	 */
	public void reset() {
		this.reset(true);
	}
	
	
	/**
	 * Reset the health/armor/etc.
	 * @param resetScore
	 */
	public void reset(boolean resetScore) {
		this.health = 100;
		this.armor = 0;
		//setDamageFactor(1);
		if(resetScore) setScore(0);
		setVehicle(null);
		
		animation.reset();
		// Stand up again
		animation.stand();
		
		setToStart();
		
		
		// reset Portals
		resetWeapons();
		
		
		// Reset Zombie if the player had one
		resetZombie(true);
		
		// Reset Treasures
		resetTreasures();
	}
	
	
	/**
	 * The Reset-Method for the Server if a client dies.
	 * Since it would never call reset, cause it doesnt has a controller, we need to call this
	 * @param resetScore
	 */
	public void resetServer(boolean resetScore) {
		this.health = 100;
		this.armor = 0;
		//setDamageFactor(1);
		if(resetScore) setScore(0);
		setVehicle(null);
		
		
		// Remove Weapons
		resetWeapons();
		
		// Reset Zombie if the player had one
		//resetZombie(true);
		
		resetTreasures();
	}
	
	
	
	public void resetTreasures() {
		Vector<Integer> treasuresReset = new Vector<Integer>();
		
		if(treasuresFound.size() > 0) {
			for(Treasure t:treasuresFound) {
				t.reset();
				treasuresReset.add(t.getIndex());
			}
		}
		treasuresFound.clear();
		
		if(treasuresReset.size() > 0) {
			TreasureResetMessage trm = new TreasureResetMessage();
			trm.setTreasureIndizes(treasuresReset);
			BasicSyncManager syncManager = 
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
			if(syncManager instanceof Server) {
				((Server)syncManager).getServer().sendToAll(trm);
			}else{
				((Client)syncManager).getClient().sendToServer(trm);
			}
		}
	}
	
	
	
	public void resetZombie(boolean sendMessage) {
		if(hasZombie()) {
			hasZombie = false;
			
			RotwGameState gameState = ((RotwGameState)GameStateManager.getInstance().getChild("gameState"));
			
			int otherZombieIndex = (getTeam() == TEAM_ONE) ? 1:0;
			((CaptureTheZombie)gameState.getGameMode()).getZombie(otherZombieIndex).reset();
			if(sendMessage) ((CaptureTheZombie)gameState.getGameMode())
				.getZombie(otherZombieIndex).sendInfoMessage(ZombieInfoMessage.ZOMBIE_LOST);
			
			// Hide Zombie-Icon
			((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getHUD().hideZombie();
		}
	}
	
	
	public void resetWeapons() {
		weapons.clear();
		
		String[] defaultWeapons = null;
		if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")) == null) {
			defaultWeapons = new String[]{"Knife","Pistol","Portal"};
		}else{
			defaultWeapons = ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getDefaultWeapons();
		}
		
        for(String w:defaultWeapons) {
        	Weapon weapon = null;
        	if(w.equals("Knife")) {
        		weapon = new Knife();
        	}else if(w.equals("Pistol")) {
        		weapon = new Pistol();
        	}else if(w.equals("Portal")) {
        		if(portalGun == null) portalGun = new PortalGun();
        		else portalGun.reset();
        		weapon = portalGun;
        	}else if(w.equals("Uzi")) {
        		weapon = new Uzi();
        	}else if(w.equals("Rifle")) {
        		weapon = new Rifle();
        	}else if(w.equals("SmokeGrenade")) {
        		weapon = new SmokeGrenade();
        	}else if(w.equals("HEGrenade")) {
        		weapon = new HEGrenade();
        	}
        	
        	if(weapon != null) weapons.add(weapon);
        }
		this.currentWeapon = 0;
	}
	
	
	
	
	
	/**
	 * Set the player to a randomly selected starting position
	 */
	public void setToStart() {		
		// Select Random Entry
		Random r = new Random();
		int startPoint = r.nextInt(startPos.get(getTeam()).size());
		
		Vector3f startloc = startPos.get(getTeam()).get(startPoint).clone();
		startloc.subtractLocal(getBody().getLocalTranslation().mult(0.24f));
		this.setLocalTranslation(startloc);
		//this.setLocalTranslation(new Vector3f(5,10,-20));
	}
	
	
	
	
	/**
	 * Gets called by a Weapon if you fired
	 */
	public void fired() {
		animation.fireAnimation();
	}

	
	
	
	
	

	/**
	 * Damage done by falling
	 * @param distance the distance you fell
	 */
	public void fallDamage(float distance) {
		if(isGodMode()) return ;
		
		if(distance > GlobalSettings.FALLHEIGHTTODIE) {
			diedFalling();
			return ;
		}
		
		int damage = (int)Math.round((2*distance)/3.0f);
		health -= damage;
		
		if(health <= 0) {
			diedFalling();
		}
	}

	
	

	/**
	 * The Player got hit
	 * only the server should call that, cause he is the only one who really knows the player
	 * the got hit, cause he has the bulletcontroller
	 * @param player the one who fired the shot
	 */
	public void hit(Person player, int damage) {
		if(isDead()) return ;
		if(isGodMode()) return ;
		
		if(getArmor() > 0) {
			armor -= damage;
			if(armor < 0) armor = 0;
		}else{
			health -= damage;
		}
		
		if(player != null && health <= 0) {
			player.killedOpponent(this.getPlayerName());
			died(player.getPlayerName(), player.getCameraPosition());
		}
	}


	/**
	 * The Player got hit
	 * @param playerName the one who fired the shot
	 */
	public void hit(String playerName, int damage, Vector3f playerLocation) {
		if(health <= 0) return ;
		if(isGodMode()) return ;
		
		hit((Person)null,damage);
		
		if(health <= 0) {
			died(playerName, playerLocation);
		}
	}
	

	
	/**
	 * Player killed a Opponent
	 * @param opp the opponent you killed
	 */
	public void killedOpponent(String oppName) {
		if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
			// Tell Client that he killed someone
			BasicSyncManager syncManager = 
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
			KilledOppMessage msg = new KilledOppMessage();
			msg.setPlayerName(oppName);
			((Server)syncManager).getServer().sendToPlayer(msg, getPlayerId());
		}
		
		increaseScore();
	}
	
	
	/**
	 * The Person died.
	 * Reset position...
	 * @param playerName the name of the player that killed us
	 */
	public void died(String playerName, Vector3f playerLocation) {
		//decreaseScore();
		animation.fall();
		this.lastTimeDied = System.currentTimeMillis();
		resetAfterDeath = true;
		
		// Reset if this is a server and the person is not the player
		if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer() &&
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getPlayer() != this) {
			resetServer(false);
		}
		
		//reset(false);
	}
	
	
	public void diedFalling() {
		decreaseScore();
		animation.fall();
		this.lastTimeDied = System.currentTimeMillis();
		resetAfterDeath = true;
		
		// Reset the player-position if this is a server and the person is not the player
		// cause we dont have a controller for the person
		if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer() &&
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getPlayer() != this) {
			resetServer(false);
		}
	}
	
	
	

	/**
	 * Refill the Ammo
	 */
	public void refillAmmo() {
		for(Weapon w:weapons) {
			w.refillAmmo();
		}
	}
	
	
	
	public boolean increaseHealth() {
		if((System.currentTimeMillis() - lastHealthInc) > GlobalSettings.HEALTHINCREASELAG) {
			if(health < 100) health += GlobalSettings.HEALTHINCREASE;
			lastHealthInc = System.currentTimeMillis();
			return true;
		}
		return false;
	}
	
	
	public void setArmor(int armor) {
		this.armor = armor;
		if(armor < 0) armor = 0;
	}
	
	public void setFullArmor() {
		this.armor = GlobalSettings.MAXARMOR;
	}
	

	
	/**
	 * Set Start-Position
	 * @param start the vector3f-array contains all the starting-positions for the current map
	 */
	@SuppressWarnings("unchecked")
	public void setStartPosition(Hashtable<Integer,Vector<Vector3f>> start) {
		this.startPos = (Hashtable<Integer,Vector<Vector3f>>)start.clone();

		if(!((RotwGameState)GameStateManager.getInstance().getChild("gameState")).useTeams()) {
			if(team == Person.TEAM_ONE) {
				startPos.get(Person.TEAM_ONE).addAll(startPos.get(Person.TEAM_TWO));
			}else{
				startPos.get(Person.TEAM_TWO).addAll(startPos.get(Person.TEAM_ONE));
			}
		}
	}
	
	
	
	public int getArmor() {
		return armor;
	}
	
	public void increaseScore() {
		score++;
		updateScore();
	}
	
	public void decreaseScore() {
		score--;
		updateScore();
	}
	
	
	public void killedZombie() {
		score++;
		updateScore();
	}

	public void foundAllTreasures() {
		score += 3;
		updateScore();
	}
	
	public void setScore(int score) {
		boolean update = false;
		if(this.score != score) {
			update = true;
		}
		
		this.score = score;
		
		if(update) {
			updateScore();
		}
	}
	
	
	/**
	 * Updates the Score if this is a Server
	 */
	public void updateScore() {
		if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
			BasicSyncManager syncManager = 
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
			((Server)syncManager).updateScore();
		}
	}
	
	
	
	/**
	 * Is the Player dead?
	 * @return true if he is dead
	 */
	public boolean isDead() {
		if((System.currentTimeMillis() - lastTimeDied) < 5000 ) {
			return true;
		}
		return false;
	}
	
	
	
	
	/**
	 * WEAPON - Stuff
	 */
	public void addWeapon(Weapon w) {
		weapons.add(w);
	}
	
	
	public void nextWeapon() {
		currentWeapon++;
		
		if(currentWeapon == weapons.size()) {
			currentWeapon = 0;
		}
	}
	
	public void previousWeapon() {
		currentWeapon--;
		
		if(currentWeapon < 0) {
			currentWeapon = weapons.size() - 1;
		}
	}
	
	public void setWeapon(int index) {
		if(index < 0) return ;
		if(index > (weapons.size() - 1)) return ;
		currentWeapon = index;
	}

	public Weapon getCurrentWeapon() {
		if(currentWeapon < weapons.size()) {
			return weapons.get(currentWeapon);
		}
		return null;
	}
	
	public Weapon getWeapon(Class<? extends Weapon> c) {
		for(Weapon w:weapons) {
			if(c.isInstance(w))
				return w;
		}
		return null;
	}
	
	
	
	
	
	public int getScore() {
		return score;
	}

	public JointController getController() {
		return animation.getController();
	}
	
	
	public Node getBody() {
		return body;
	}
	
	public int getHealth() {
		return health;
	}
	
	public void setHealth(int health) {
		this.health = health;
	}

	
	
	public short getPlayerId() {
		return playerId;
	}


	public void setPlayerId(short playerId) {
		this.playerId = playerId;
	}

	

	
	public void setBulletColor(ColorRGBA color) {
		this.bulletColor = color;
	}

	
	public ColorRGBA getBulletColor() {
		return bulletColor;
	}
	
	public String getPlayerName() {
		return playerName;
	}


	public void setPlayerName(String playerName) {
		this.playerName = playerName;
	}



	public String getTextureColor() {
		return textureColor;
	}

	public void setTextureColor(String textureColor) {
		this.textureColor = textureColor;
		
		// reload model
		loadModel();
	}
	
	
	public boolean shouldReset() {
		return this.resetAfterDeath;
	}
	
	public void setResetAfterDeath(boolean reset) {
		this.resetAfterDeath = reset;
	}


	public PersonAnimation getAnimation() {
		return animation;
	}


	public int getTeam() {
		return team;
	}


	public void setTeam(int team) {
		if(!((RotwGameState)GameStateManager.getInstance().getChild("gameState")).useTeams()) {
			this.team = 1;
			return ;
		}
		
		if(this.team == team) return ;
		
		this.team = team;
		
		Player player = ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getPlayer();
		
		if(this != player) {
			if(player.getTeam() == this.getTeam()) {
				showTeamBox();
			}else{
				hideTeamBox();
			}
		}
	}
	
	public void showTeamBox() {
		teamBox.setCullHint(CullHint.Never);
	}

	public void hideTeamBox() {
		teamBox.setCullHint(CullHint.Always);
	}
	
	
	
	public static void updateAllTeamBoxes() {
		Player player = ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getPlayer();
		
		Enumeration<Person> e = OpponentList.getInstance().getOpponents().elements();
		
		while(e.hasMoreElements()) {
			Person p = e.nextElement();
			
			if(p != player) {
				if(player.getTeam() == p.getTeam() && ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).useTeams()) {
					p.showTeamBox();
				}else{
					p.hideTeamBox();
				}
			}
		}
	}


	
	public boolean hasZombie() {
		return hasZombie;
	}


	public void setHasZombie(boolean hasZombie) {
		this.hasZombie = hasZombie;
	}


	public boolean hasVehicle() {
		return (vehicle != null);
	}


	public Vehicle getVehicle() {
		return vehicle;
	}


	public void setVehicle(Vehicle vehicle) {
		this.vehicle = vehicle;
	}


	public long getDoubleDamageTime() {
		return doubleDamageTime;
	}


	public void updateDoubleDamageTime() {
		this.doubleDamageTime = System.currentTimeMillis();
	}
	
	public boolean isDoubleDamage() {
		if( (System.currentTimeMillis() - doubleDamageTime) < Person.DOUBLEDAMAGEDURATION) {
			return true;
		}
		return false;
	}
	
	
	public void addTreasure(Treasure t) {
		treasuresFound.add(t);
	}
	
	public int getTreasureCount() {
		return treasuresFound.size();
	}


	public void setWalkingDirection(Vector3f walkingDirection) {
		this.walkingDirection = walkingDirection;
	}


	public Vector3f getWalkingDirection() {
		return walkingDirection;
	}


	public PhysicsNode getBodyPhysicsNode() {
		return bodyPhysicsNode;
	}


	public void setBodyPhysicsNode(PhysicsNode bodyPhysicsNode) {
		this.bodyPhysicsNode = bodyPhysicsNode;
	}


	public int getDamageFactor() {
		return damageFactor;
	}


	public void setDamageFactor(int damageFactor) {
		this.damageFactor = damageFactor;
	}


	public boolean isGodMode() {
		return godMode;
	}


	public void setGodMode(boolean godMode) {
		this.godMode = godMode;
	}
}
